/*-------------------------------------------------------------------------
 *
 * nodeTidscan.c
 *	  支持关系的直接 tid 扫描的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeTidscan.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 *
 *		ExecTidScan			使用 tids 扫描关系
 *		ExecInitTidScan		创建并初始化状态信息。
 *		ExecReScanTidScan		重新扫描 tid 关系。
 *		ExecEndTidScan		释放所有存储。
 */
#include "postgres.h"

#include "access/sysattr.h"
#include "access/tableam.h"
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
#include "executor/nodeTidscan.h"
#include "lib/qunique.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "utils/array.h"
#include "utils/rel.h"


#define IsCTIDVar(node)  \
	((node) != NULL && \
	 IsA((node), Var) && \
	 ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
	 ((Var *) (node))->varlevelsup == 0)

/* tss_tidexprs 中的一个元素 */
typedef struct TidExpr
{
	ExprState  *exprstate;		/* 产生 TID 的子表达式的 ExprState */
	bool		isarray;		/* 如果为真，它将产生 tid[] 而不仅仅是 tid */
	CurrentOfExpr *cexpr;		/* 另外，我们可以使用 CURRENT OF */
} TidExpr;

static void fc_TidExprListCreate(TidScanState *fc_tidstate);
static void fc_TidListEval(TidScanState *fc_tidstate);
static int	fc_itemptr_comparator(const void *fc_a, const void *fc_b);
static TupleTableSlot *fc_TidNext(TidScanState *fc_node);


/*
 * 提取产生 TIDs 以进行搜索的 qual 子表达式，
 * 并在它们是普通表达式的情况下将其编译为 ExprStates。
 *
 * CURRENT OF 是一个特殊情况，我们无法有效地编译它；
 * 只需将其原样放入 TidExpr 列表中。
 */
static void fc_TidExprListCreate(TidScanState *fc_tidstate)
{
	TidScan    *fc_node = (TidScan *) fc_tidstate->ss.ps.plan;
	ListCell   *fc_l;

	fc_tidstate->tss_tidexprs = NIL;
	fc_tidstate->tss_isCurrentOf = false;

	foreach(fc_l, fc_node->tidquals)
	{
		Expr	   *fc_expr = (Expr *) lfirst(fc_l);
		TidExpr    *fc_tidexpr = (TidExpr *) palloc0(sizeof(TidExpr));

		if (is_opclause(fc_expr))
		{
			Node	   *fc_arg1;
			Node	   *fc_arg2;

			fc_arg1 = get_leftop(fc_expr);
			fc_arg2 = get_rightop(fc_expr);
			if (IsCTIDVar(fc_arg1))
				fc_tidexpr->exprstate = ExecInitExpr((Expr *) fc_arg2,
												  &fc_tidstate->ss.ps);
			else if (IsCTIDVar(fc_arg2))
				fc_tidexpr->exprstate = ExecInitExpr((Expr *) fc_arg1,
												  &fc_tidstate->ss.ps);
			else
				elog(ERROR, "could not identify CTID variable");
			fc_tidexpr->isarray = false;
		}
		else if (fc_expr && IsA(fc_expr, ScalarArrayOpExpr))
		{
			ScalarArrayOpExpr *fc_saex = (ScalarArrayOpExpr *) fc_expr;

			Assert(IsCTIDVar(linitial(fc_saex->args)));
			fc_tidexpr->exprstate = ExecInitExpr(lsecond(fc_saex->args),
											  &fc_tidstate->ss.ps);
			fc_tidexpr->isarray = true;
		}
		else if (fc_expr && IsA(fc_expr, CurrentOfExpr))
		{
			CurrentOfExpr *fc_cexpr = (CurrentOfExpr *) fc_expr;

			fc_tidexpr->cexpr = fc_cexpr;
			fc_tidstate->tss_isCurrentOf = true;
		}
		else
			elog(ERROR, "could not identify CTID expression");

		fc_tidstate->tss_tidexprs = lappend(fc_tidstate->tss_tidexprs, fc_tidexpr);
	}

	/* CurrentOfExpr 永远不会与其他任何内容 OR 连接 */
	Assert(list_length(fc_tidstate->tss_tidexprs) == 1 ||
		   !fc_tidstate->tss_isCurrentOf);
}

/*
 * 通过评估表达式来计算要访问的 TID 列表。
 *
 * （结果实际上是一个数组，而不是一个列表。）
 */
static void fc_TidListEval(TidScanState *fc_tidstate)
{
	ExprContext *fc_econtext = fc_tidstate->ss.ps.ps_ExprContext;
	TableScanDesc fc_scan;
	ItemPointerData *fc_tidList;
	int			fc_numAllocTids;
	int			fc_numTids;
	ListCell   *fc_l;

	/*
	 * 按需开始扫描 - 初始化扫描并不是免费的（例如，堆统计
	 * 表的大小），因此推迟到需要时进行是有道理的 -
	 * 该节点可能永远不会被执行。
	 */
	if (fc_tidstate->ss.ss_currentScanDesc == NULL)
		fc_tidstate->ss.ss_currentScanDesc =
			table_beginscan_tid(fc_tidstate->ss.ss_currentRelation,
								fc_tidstate->ss.ps.state->es_snapshot);
	fc_scan = fc_tidstate->ss.ss_currentScanDesc;

	/*
	 * 我们用足够的插槽初始化数组，以防所有 quals
	 * 是简单的 OpExprs 或 CurrentOfExprs。 如果有任何
	 * ScalarArrayOpExprs，我们可能需要扩大数组。
	 */
	fc_numAllocTids = list_length(fc_tidstate->tss_tidexprs);
	fc_tidList = (ItemPointerData *)
		palloc(fc_numAllocTids * sizeof(ItemPointerData));
	fc_numTids = 0;

	foreach(fc_l, fc_tidstate->tss_tidexprs)
	{
		TidExpr    *fc_tidexpr = (TidExpr *) lfirst(fc_l);
		ItemPointer fc_itemptr;
		bool		fc_isNull;

		if (fc_tidexpr->exprstate && !fc_tidexpr->isarray)
		{
			fc_itemptr = (ItemPointer)
				DatumGetPointer(ExecEvalExprSwitchContext(fc_tidexpr->exprstate,
														  fc_econtext,
														  &fc_isNull));
			if (fc_isNull)
				continue;

			/*
			 * 我们默默丢弃任何被 AM 视为无效的 TIDs
			 * （例如，对于堆来说，在扫描开始时它们可能超出范围。
			 * 由于我们至少对表持有 AccessShareLock，因此
			 * 不可能有人截断我们打算访问的块。）。
			 */
			if (!table_tuple_tid_valid(fc_scan, fc_itemptr))
				continue;

			if (fc_numTids >= fc_numAllocTids)
			{
				fc_numAllocTids *= 2;
				fc_tidList = (ItemPointerData *)
					repalloc(fc_tidList,
							 fc_numAllocTids * sizeof(ItemPointerData));
			}
			fc_tidList[fc_numTids++] = *fc_itemptr;
		}
		else if (fc_tidexpr->exprstate && fc_tidexpr->isarray)
		{
			Datum		fc_arraydatum;
			ArrayType  *fc_itemarray;
			Datum	   *fc_ipdatums;
			bool	   *fc_ipnulls;
			int			fc_ndatums;
			int			fc_i;

			fc_arraydatum = ExecEvalExprSwitchContext(fc_tidexpr->exprstate,
												   fc_econtext,
												   &fc_isNull);
			if (fc_isNull)
				continue;
			fc_itemarray = DatumGetArrayTypeP(fc_arraydatum);
			deconstruct_array(fc_itemarray,
							  TIDOID, sizeof(ItemPointerData), false, TYPALIGN_SHORT,
							  &fc_ipdatums, &fc_ipnulls, &fc_ndatums);
			if (fc_numTids + fc_ndatums > fc_numAllocTids)
			{
				fc_numAllocTids = fc_numTids + fc_ndatums;
				fc_tidList = (ItemPointerData *)
					repalloc(fc_tidList,
							 fc_numAllocTids * sizeof(ItemPointerData));
			}
			for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
			{
				if (fc_ipnulls[fc_i])
					continue;

				fc_itemptr = (ItemPointer) DatumGetPointer(fc_ipdatums[fc_i]);

				if (!table_tuple_tid_valid(fc_scan, fc_itemptr))
					continue;

				fc_tidList[fc_numTids++] = *fc_itemptr;
			}
			pfree(fc_ipdatums);
			pfree(fc_ipnulls);
		}
		else
		{
			ItemPointerData fc_cursor_tid;

			Assert(fc_tidexpr->cexpr);
			if (execCurrentOf(fc_tidexpr->cexpr, fc_econtext,
							  RelationGetRelid(fc_tidstate->ss.ss_currentRelation),
							  &fc_cursor_tid))
			{
				if (fc_numTids >= fc_numAllocTids)
				{
					fc_numAllocTids *= 2;
					fc_tidList = (ItemPointerData *)
						repalloc(fc_tidList,
								 fc_numAllocTids * sizeof(ItemPointerData));
				}
				fc_tidList[fc_numTids++] = fc_cursor_tid;
			}
		}
	}

	/*
	 * 将 TID 数组排序并消除重复项。
	 * 消除重复项是必要的，因为我们希望在列表中具有 OR 语义。
	 * 排序使检测重复项变得更容易，并且作为额外的好处
	 * 确保我们以最有效的方式访问堆。
	 */
	if (fc_numTids > 1)
	{
		/* CurrentOfExpr 永远不会与其他任何内容 OR 连接 */
		Assert(!fc_tidstate->tss_isCurrentOf);

		qsort((void *) fc_tidList, fc_numTids, sizeof(ItemPointerData),
			  fc_itemptr_comparator);
		fc_numTids = qunique(fc_tidList, fc_numTids, sizeof(ItemPointerData),
						  fc_itemptr_comparator);
	}

	fc_tidstate->tss_TidList = fc_tidList;
	fc_tidstate->tss_NumTids = fc_numTids;
	fc_tidstate->tss_TidPtr = -1;
}

/*
 * qsort 比较器对于 ItemPointerData 项
 */
static int fc_itemptr_comparator(const void *fc_a, const void *fc_b)
{
	const ItemPointerData *fc_ipa = (const ItemPointerData *) fc_a;
	const ItemPointerData *fc_ipb = (const ItemPointerData *) fc_b;
	BlockNumber fc_ba = ItemPointerGetBlockNumber(fc_ipa);
	BlockNumber fc_bb = ItemPointerGetBlockNumber(fc_ipb);
	OffsetNumber fc_oa = ItemPointerGetOffsetNumber(fc_ipa);
	OffsetNumber fc_ob = ItemPointerGetOffsetNumber(fc_ipb);

	if (fc_ba < fc_bb)
		return -1;
	if (fc_ba > fc_bb)
		return 1;
	if (fc_oa < fc_ob)
		return -1;
	if (fc_oa > fc_ob)
		return 1;
	return 0;
}

/* ----------------------------------------------------------------
 *		TidNext
 *
 *		从 TidScan 节点的 currentRelation 检索元组
 *		使用 TidScanState 信息中的 tids。
 *
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_TidNext(TidScanState *fc_node)
{
	EState	   *fc_estate;
	ScanDirection fc_direction;
	Snapshot	fc_snapshot;
	TableScanDesc fc_scan;
	Relation	fc_heapRelation;
	TupleTableSlot *fc_slot;
	ItemPointerData *fc_tidList;
	int			fc_numTids;
	bool		fc_bBackward;

	/*
	 * 从 tid 扫描节点提取必要信息
	 */
	fc_estate = fc_node->ss.ps.state;
	fc_direction = fc_estate->es_direction;
	fc_snapshot = fc_estate->es_snapshot;
	fc_heapRelation = fc_node->ss.ss_currentRelation;
	fc_slot = fc_node->ss.ss_ScanTupleSlot;

	/*
	 * 第一次通过，计算要访问的 TIDs 列表
	 */
	if (fc_node->tss_TidList == NULL)
		fc_TidListEval(fc_node);

	fc_scan = fc_node->ss.ss_currentScanDesc;
	fc_tidList = fc_node->tss_TidList;
	fc_numTids = fc_node->tss_NumTids;

	/*
	 * 初始化或根据方向推进扫描位置。
	 */
	fc_bBackward = ScanDirectionIsBackward(fc_direction);
	if (fc_bBackward)
	{
		if (fc_node->tss_TidPtr < 0)
		{
			/* 为反向扫描初始化 */
			fc_node->tss_TidPtr = fc_numTids - 1;
		}
		else
			fc_node->tss_TidPtr--;
	}
	else
	{
		if (fc_node->tss_TidPtr < 0)
		{
			/* 为前向扫描初始化 */
			fc_node->tss_TidPtr = 0;
		}
		else
			fc_node->tss_TidPtr++;
	}

	while (fc_node->tss_TidPtr >= 0 && fc_node->tss_TidPtr < fc_numTids)
	{
		ItemPointerData fc_tid = fc_tidList[fc_node->tss_TidPtr];

		/*
		 * 对于 WHERE CURRENT OF，从游标检索的元组可能
		 * 已经被更新；如果是这样，我们应该获取根据我们快照
		 * 的当前版本。
		 */
		if (fc_node->tss_isCurrentOf)
			table_tuple_get_latest_tid(fc_scan, &fc_tid);

		if (table_tuple_fetch_row_version(fc_heapRelation, &fc_tid, fc_snapshot, fc_slot))
			return fc_slot;

		/* 坏的 TID 或快照 qual 失败；尝试下一个 */
		if (fc_bBackward)
			fc_node->tss_TidPtr--;
		else
			fc_node->tss_TidPtr++;

		CHECK_FOR_INTERRUPTS();
	}

	/*
	 * 如果我们到这里，意味着 tid 扫描失败，因此我们位于扫描的末尾。
	 */
	return ExecClearTuple(fc_slot);
}

/*
 * TidRecheck -- 访问方法例程以在 EvalPlanQual 中重新检查元组
 */
static bool fc_TidRecheck(TidScanState *fc_node, TupleTableSlot *fc_slot)
{
	/*
	 * XXX 我们是否应该在这里检查以确保元组匹配 TID 列表？在
	 * 运行时键情况下，这并不确定，是吗？然而，在 WHERE
	 * CURRENT OF 的情况下，它可能根本不匹配...
	 */
	return true;
}


/* ----------------------------------------------------------------
 *		ExecTidScan(node)
 *
 *		使用 tids 扫描关系并返回
 *		   以指定的方向下一个符合条件的元组。
 *		我们调用 ExecScan() 例程并传递适当的
 *		访问方法函数。
 *
 *		条件：
 *		  -- AMI 维护的“游标”位于先前返回的元组上。
 *
 *		初始状态：
 *		  -- 指示的关系已打开以进行扫描，因此“游标”
 *			 定位在第一个符合条件的元组之前。
 *		  -- tss_TidPtr 为 -1。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecTidScan(PlanState *fc_pstate)
{
	TidScanState *fc_node = castNode(TidScanState, fc_pstate);

	return ExecScan(&fc_node->ss,
					(ExecScanAccessMtd) fc_TidNext,
					(ExecScanRecheckMtd) fc_TidRecheck);
}

/* ----------------------------------------------------------------
 *		ExecReScanTidScan(node)
 * ----------------------------------------------------------------
 */
void ExecReScanTidScan(TidScanState *fc_node)
{
	if (fc_node->tss_TidList)
		pfree(fc_node->tss_TidList);
	fc_node->tss_TidList = NULL;
	fc_node->tss_NumTids = 0;
	fc_node->tss_TidPtr = -1;

	/* 实际上不是必需的，但似乎是良好的形式 */
	if (fc_node->ss.ss_currentScanDesc)
		table_rescan(fc_node->ss.ss_currentScanDesc, NULL);

	ExecScanReScan(&fc_node->ss);
}

/* ----------------------------------------------------------------
 *		ExecEndTidScan
 *
 *		释放通过 C 例程分配的任何存储。
 *		不返回任何内容。
 * ----------------------------------------------------------------
 */
void ExecEndTidScan(TidScanState *fc_node)
{
	if (fc_node->ss.ss_currentScanDesc)
		table_endscan(fc_node->ss.ss_currentScanDesc);

	/*
	 * 释放表达式上下文
	 */
	ExecFreeExprContext(&fc_node->ss.ps);

	/*
	 * 清空元组表插槽
	 */
	if (fc_node->ss.ps.ps_ResultTupleSlot)
		ExecClearTuple(fc_node->ss.ps.ps_ResultTupleSlot);
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);
}

/* ----------------------------------------------------------------
 *		ExecInitTidScan
 *
 *		初始化 tid 扫描的状态信息，创建
 *		扫描键，并打开基础和 tid 关系。
 *
 *		参数：
 *		  node: 由规划器生成的 TidScan 节点。
 *		  estate: 在 InitPlan 中初始化的执行状态。
 * ----------------------------------------------------------------
 */
TidScanState *
ExecInitTidScan(TidScan *fc_node, EState *fc_estate, int fc_eflags)
{
	TidScanState *fc_tidstate;
	Relation	fc_currentRelation;

	/*
	 * 创建状态结构
	 */
	fc_tidstate = makeNode(TidScanState);
	fc_tidstate->ss.ps.plan = (Plan *) fc_node;
	fc_tidstate->ss.ps.state = fc_estate;
	fc_tidstate->ss.ps.ExecProcNode = fc_ExecTidScan;

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_tidstate->ss.ps);

	/*
	 * 将 tid 列表标记为尚未计算
	 */
	fc_tidstate->tss_TidList = NULL;
	fc_tidstate->tss_NumTids = 0;
	fc_tidstate->tss_TidPtr = -1;

	/*
	 * 打开扫描关系
	 */
	fc_currentRelation = ExecOpenScanRelation(fc_estate, fc_node->scan.scanrelid, fc_eflags);

	fc_tidstate->ss.ss_currentRelation = fc_currentRelation;
	fc_tidstate->ss.ss_currentScanDesc = NULL; /* 此处没有堆扫描 */

	/*
	 * 从关系描述符获取扫描类型。
	 */
	ExecInitScanTupleSlot(fc_estate, &fc_tidstate->ss,
						  RelationGetDescr(fc_currentRelation),
						  table_slot_callbacks(fc_currentRelation));

	/*
	 * 初始化结果类型和投影。
	 */
	ExecInitResultTypeTL(&fc_tidstate->ss.ps);
	ExecAssignScanProjectionInfo(&fc_tidstate->ss);

	/*
	 * 初始化子表达式
	 */
	fc_tidstate->ss.ps.qual =
		ExecInitQual(fc_node->scan.plan.qual, (PlanState *) fc_tidstate);

	fc_TidExprListCreate(fc_tidstate);

	/*
	 * 完成所有操作。
	 */
	return fc_tidstate;
}
